module tlb
#(
parameter TLBNUM = 16
)
(
input wire clk,
input wire resetn,

// search port 0 (for fetch)
input wire [ 18:0] s0_vppn,
input wire [ 9:0] s0_asid,
input wire        s0_va_bit12,
output wire s0_found,
output wire [$clog2(TLBNUM)-1:0] s0_index,
output wire [ 19:0] s0_ppn,
output wire [ 5:0] s0_ps,
output wire [ 1:0] s0_mat,
output wire s0_d,
output wire s0_v,
output wire [1:0]  s0_plv,

// search port 1 (for load/store)
input wire [ 18:0] s1_vppn,
input wire [ 9:0] s1_asid,
input wire        s1_va_bit12,
output wire s1_found,
output wire [$clog2(TLBNUM)-1:0] s1_index,
output wire [ 19:0] s1_ppn,
output wire [ 5:0] s1_ps,
output wire [ 1:0] s1_mat,
output wire s1_d,
output wire s1_v,
output wire [1:0]  s1_plv,

// invtlb opcode
input wire invtlb_valid,
input wire [ 4:0] invtlb_op,

// write port
input wire we, //w(rite) e(nable)
input wire [$clog2(TLBNUM)-1:0] w_index,
input wire w_e,
input wire [ 18:0] w_vppn,
input wire [ 9:0] w_asid,
input wire w_g,
input wire [ 5:0] w_ps,
input wire [ 19:0] w_ppn0,
input wire [ 1:0] w_plv0,
input wire [ 1:0] w_mat0,
input wire w_d0,
input wire w_v0,
input wire [ 19:0] w_ppn1,
input wire [ 1:0] w_plv1,
input wire [ 1:0] w_mat1,
input wire w_d1,
input wire w_v1,

// read port
input wire [$clog2(TLBNUM)-1:0] r_index,
output wire r_e,
output wire [ 18:0] r_vppn,
output wire [ 5:0] r_ps,
output wire [ 9:0] r_asid,
output wire r_g,
output wire [ 19:0] r_ppn0,
output wire [ 1:0] r_plv0,
output wire [ 1:0] r_mat0,
output wire r_d0,
output wire r_v0,
output wire [ 19:0] r_ppn1,
output wire [ 1:0] r_plv1,
output wire [ 1:0] r_mat1,
output wire r_d1,
output wire r_v1
);

reg [TLBNUM-1:0] tlb_e;
reg [TLBNUM-1:0] tlb_ps4MB; //pagesize 1:4MB, 0:4KB
reg [ 18:0] tlb_vppn [TLBNUM-1:0];
reg [ 9:0] tlb_asid [TLBNUM-1:0];
reg tlb_g [TLBNUM-1:0];
reg [ 19:0] tlb_ppn0 [TLBNUM-1:0];
reg [ 1:0] tlb_plv0 [TLBNUM-1:0];
reg [ 1:0] tlb_mat0 [TLBNUM-1:0];
reg tlb_d0 [TLBNUM-1:0];
reg tlb_v0 [TLBNUM-1:0];
reg [ 19:0] tlb_ppn1 [TLBNUM-1:0];
reg [ 1:0] tlb_plv1 [TLBNUM-1:0];
reg [ 1:0] tlb_mat1 [TLBNUM-1:0];
reg tlb_d1 [TLBNUM-1:0];
reg tlb_v1 [TLBNUM-1:0];

wire        [TLBNUM-1:0] cond1;
wire        [TLBNUM-1:0] cond2;
wire        [TLBNUM-1:0] cond3;
wire        [TLBNUM-1:0] cond4;
wire[TLBNUM-1:0]match0;
wire[TLBNUM-1:0]match1;

wire [$clog2(TLBNUM)-1:0] s0_index_arr [TLBNUM -1:0];
wire [$clog2(TLBNUM)-1:0] s1_index_arr [TLBNUM -1:0];

genvar tlb_i;
generate for (tlb_i = 0; tlb_i < TLBNUM; tlb_i = tlb_i + 1) begin:gen_tlb
assign match0[tlb_i] = (s0_vppn[18:10] == tlb_vppn[tlb_i][18:10])&&(tlb_ps4MB[tlb_i]||s0_vppn[9:0]==tlb_vppn[tlb_i][9:0])
                                                                &&((s0_asid==tlb_asid[tlb_i]) || tlb_g[tlb_i])&&(tlb_e[tlb_i]!=1'd0);
assign match1[tlb_i] = (s1_vppn[18:10] == tlb_vppn[tlb_i][18:10])&&(tlb_ps4MB[tlb_i]||s1_vppn[9:0]==tlb_vppn[tlb_i][9:0])
                                                                &&((s1_asid==tlb_asid[tlb_i]) || tlb_g[tlb_i])&&(tlb_e[tlb_i]!=1'd0); 
assign cond1 [tlb_i] = (tlb_g[tlb_i] == 1'd0);
assign cond2 [tlb_i] = (tlb_g[tlb_i] == 1'd1);
assign cond3 [tlb_i] = (s1_asid == tlb_asid[tlb_i]);
assign cond4 [tlb_i] =  (s1_vppn[18:10] == tlb_vppn[tlb_i][18:10])&&(tlb_ps4MB[tlb_i]||s1_vppn[9:0]==tlb_vppn[tlb_i][9:0]);
if (tlb_i == 0) begin
        assign s0_index_arr[tlb_i] = {$clog2(TLBNUM){match0[tlb_i]}} & tlb_i;
        assign s1_index_arr[tlb_i] = {$clog2(TLBNUM){match1[tlb_i]}} & tlb_i;
    end else begin
        assign s0_index_arr[tlb_i] = s0_index_arr[tlb_i - 1] | ({$clog2(TLBNUM){match0[tlb_i]}} & tlb_i);
        assign s1_index_arr[tlb_i] = s1_index_arr[tlb_i - 1] | ({$clog2(TLBNUM){match1[tlb_i]}} & tlb_i);
    end        
always @(posedge clk) begin
    if(we&&(w_index==tlb_i))begin
        tlb_e    [tlb_i]<=w_e;
        tlb_ps4MB[tlb_i]<=(w_ps == 6'd22)?1:0;
        tlb_vppn [tlb_i]<=w_vppn;
        tlb_asid [tlb_i]<=w_asid;
        tlb_g    [tlb_i]<=w_g;
        tlb_ppn1 [tlb_i]<=w_ppn1;
        tlb_ppn0 [tlb_i]<=w_ppn0;      
        tlb_plv1 [tlb_i]<=w_plv1;
        tlb_plv0 [tlb_i]<=w_plv0;
        tlb_mat1 [tlb_i]<=w_mat1;
        tlb_mat0 [tlb_i]<=w_mat0;
        tlb_d1   [tlb_i]<=w_d1;
        tlb_d0   [tlb_i]<=w_d0;
        tlb_v1   [tlb_i]<=w_v1;
        tlb_v0   [tlb_i]<=w_v0;
    end
    else if(invtlb_valid)begin
        tlb_e    [tlb_i]<=((invtlb_op == 5'd0||invtlb_op == 5'd1)&&(cond1[tlb_i]||cond2[tlb_i])
                         ||(invtlb_op == 5'd2)                   &&(cond2[tlb_i])
                         ||(invtlb_op == 5'd3)                   &&(cond1[tlb_i])
                         ||(invtlb_op == 5'd4)                   &&(cond1[tlb_i]&&cond3[tlb_i])
                         ||(invtlb_op == 5'd5)                   &&(cond1[tlb_i]&&cond3[tlb_i]&&cond4[tlb_i])
                         ||(invtlb_op == 5'd6)                   &&((cond2[tlb_i]||cond3[tlb_i])&&cond4[tlb_i])
                                                                 )?1'd0:tlb_e[tlb_i];
    end
end
end endgenerate

//search
assign s0_found = (match0!=16'd0);
assign s1_found = (match1!=16'd0);
assign s0_index = s0_index_arr[TLBNUM -1];
assign s1_index = s1_index_arr[TLBNUM -1];
assign s0_ppn   = tlb_ps4MB[s0_index]?(s0_vppn[9]?tlb_ppn1[s0_index]:tlb_ppn0[s0_index])
                                     :(s0_va_bit12?tlb_ppn1[s0_index]:tlb_ppn0[s0_index]);
assign s1_ppn   = tlb_ps4MB[s1_index]?(s1_vppn[9]?tlb_ppn1[s1_index]:tlb_ppn0[s1_index])
                                     :(s1_va_bit12?tlb_ppn1[s1_index]:tlb_ppn0[s1_index]);
assign s0_ps    = tlb_ps4MB[s0_index]?6'd22:6'd12;
assign s1_ps    = tlb_ps4MB[s1_index]?6'd22:6'd12;
assign s0_mat   = tlb_ps4MB[s0_index]?(s0_vppn[9]?tlb_mat1[s0_index]:tlb_mat0[s0_index])
                                     :(s0_va_bit12?tlb_mat1[s0_index]:tlb_mat0[s0_index]);
assign s1_mat   = tlb_ps4MB[s1_index]?(s1_vppn[9]?tlb_mat1[s1_index]:tlb_mat0[s1_index])
                                     :(s1_va_bit12?tlb_mat1[s1_index]:tlb_mat0[s1_index]);
assign s0_d     = tlb_ps4MB[s0_index]?(s0_vppn[9]?tlb_d1[s0_index]:tlb_d0[s0_index])
                                     :(s0_va_bit12?tlb_d1[s0_index]:tlb_d0[s0_index]);
assign s1_d     = tlb_ps4MB[s1_index]?(s1_vppn[9]?tlb_d1[s1_index]:tlb_d0[s1_index])
                                     :(s1_va_bit12?tlb_d1[s1_index]:tlb_d0[s1_index]);
assign s0_v     = tlb_ps4MB[s0_index]?(s0_vppn[9]?tlb_v1[s0_index]:tlb_v0[s0_index])
                                     :(s0_va_bit12?tlb_v1[s0_index]:tlb_v0[s0_index]);
assign s1_v     = tlb_ps4MB[s1_index]?(s1_vppn[9]?tlb_v1[s1_index]:tlb_v0[s1_index])
                                     :(s1_va_bit12?tlb_v1[s1_index]:tlb_v0[s1_index]);
assign s1_plv   = tlb_ps4MB[s1_index]?(s1_vppn[9]?tlb_plv1[s1_index]:tlb_plv0[s1_index])
                                     :(s1_va_bit12?tlb_plv1[s1_index]:tlb_plv0[s1_index]);   
assign s0_plv   = tlb_ps4MB[s0_index]?(s0_vppn[9]?tlb_plv1[s0_index]:tlb_plv0[s0_index])
                                     :(s0_va_bit12?tlb_plv1[s0_index]:tlb_plv0[s0_index]);                                   

//read
assign r_e    = tlb_e[r_index];
assign r_vppn = tlb_vppn[r_index];
assign r_ps   = (tlb_ps4MB[r_index]==1'd1)?6'd22:6'd12;
assign r_asid = tlb_asid[r_index];
assign r_g    = tlb_g[r_index];
assign r_ppn0 = tlb_ppn0[r_index];
assign r_ppn1 = tlb_ppn1[r_index];
assign r_plv0 = tlb_plv0[r_index];
assign r_plv1 = tlb_plv1[r_index];
assign r_mat0 = tlb_mat0[r_index];
assign r_mat1 = tlb_mat1[r_index];
assign r_d0   = tlb_d0[r_index];
assign r_d1   = tlb_d1[r_index];
assign r_v0   = tlb_v0[r_index];
assign r_v1   = tlb_v1[r_index];

endmodule
